﻿
using System;
using System.Diagnostics;

namespace System.Timers
{   
    /// <summary>
    /// The WeeklyTimer class.
    /// </summary>
    public class WeeklyTimer : Timer
    {
        private DayOfWeek _dayofweek;
        private int _hours, _minutes, _seconds;
        private DateTime _nextschedule;
        private Timer _internaltimer;
        
        public new event ElapsedEventHandler Elapsed;
        
        private WeeklyTimer(DayOfWeek DoW, int Hours, int Minutes, int Seconds)
        {
            // Assign the time values to proper members
            AssignTimeValues(DoW, Hours, Minutes, Seconds);
            
            // Set the internal timer to check every minute
            _internaltimer = new Timer(1000*60);
            _internaltimer.Elapsed += new ElapsedEventHandler(InternalTimer_Elapsed);
        }
        
        public WeeklyTimer(DayOfWeek DoW, int Hours, int Minutes) : this(DoW, Hours, Minutes, 0)
        {
            // Do nothing
        }
        
        public WeeklyTimer(DayOfWeek DoW, int Hours) : this(DoW, Hours, 0)
        {
            // Do nothing
        }
        
        public WeeklyTimer(DayOfWeek DoW) : this(DoW, 0)
        {
            // Do nothing
        }
        
        public new void Start()
        {
            InitTimer();
            _internaltimer.Start();
        }
        
        public new void Stop()
        {
            _internaltimer.Stop();
        }
        
        public void Reschedule(DayOfWeek DoW, int Hours, int Minutes, int Seconds)
        {
            // Assign the time values to proper members
            AssignTimeValues(DoW, Hours, Minutes, Seconds);
            
            // Initialize again according to the new schedule
            InitTimer();
        }
        
        public void Reschedule(DayOfWeek DoW, int Hours, int Minutes)
        {
            this.Reschedule(DoW, Hours, Minutes, 0);
        }
        
        public void Reschedule(DayOfWeek DoW, int Hours)
        {
            this.Reschedule(DoW, Hours, 0);
        }
        
        public void Reschedule(DayOfWeek DoW)
        {
            this.Reschedule(DoW, 0);
        }
        
        protected void AssignTimeValues(DayOfWeek DoW, int Hours, int Minutes, int Seconds)
        {
            _dayofweek = DoW;
            
            if ((Hours >= 0) && (Hours <= 23)) {
                _hours = Hours;
            } else {
                throw new Exception("Invalid hours value passed to WeeklyTimer.");
            }
            
            if ((Minutes >= 0) && (Minutes <= 59)) {
                _minutes = Minutes;
            } else {
                throw new Exception("Invalid minutes value passed to WeeklyTimer.");
            }
            
            if ((Seconds >= 0) && (Seconds <= 59)) {
                _seconds = Seconds;
            } else {
                throw new Exception("Invalid seconds value passed to WeeklyTimer.");
            }
        }
        
        private void InitTimer()
        {
            DateTime now = DateTime.Now;
            DateTime dt = new DateTime(now.Year, now.Month, now.Day, _hours, _minutes, _seconds);
            
            // If the day is today
            if (DayNumber(_dayofweek) == DayNumber(now.DayOfWeek)) {
                // if time 'dt' has passed then schedule for the next week
                if (dt <= now) dt = dt.AddDays(7);
            } else {
                // If the day has yet to come in this week
                if (DayNumber(_dayofweek) > DayNumber(now.DayOfWeek)) {
                    int diff = DayNumber(_dayofweek) - DayNumber(now.DayOfWeek);
                    dt = dt.AddDays(diff);
                } else {
                    int diff = DayNumber(_dayofweek) - DayNumber(now.DayOfWeek);
                    dt = dt.AddDays(diff);
                    dt = dt.AddDays(7);
                }
            }
            
            // Set the next schedule
            TimeSpan ts = dt - now;
            _nextschedule = now.Add(ts);
        }
        
        void InternalTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (IsNowTheTime()) 
            {
                InitTimer();
                if (this.Elapsed != null)
                {
                    Elapsed(this, e);
                }
            }
        }
        
        private bool IsNowTheTime()
        {
            bool year=false, month=false, day=false, hour=false, minute=false;
            DateTime now = DateTime.Now;

            year = (now.Year == _nextschedule.Year);
            month = (now.Month == _nextschedule.Month);
            day = (now.Day == _nextschedule.Day);
            hour = (now.Hour == _nextschedule.Hour);
            minute = (now.Minute == _nextschedule.Minute);
            
            if (year && month && day && hour && minute) 
                return true;
            else
                return false;
        }
        
        private int DayNumber(DayOfWeek DoW)
        {
            switch (DoW) {
                case DayOfWeek.Monday:
                    return 1;
                case DayOfWeek.Tuesday:
                    return 2;
                case DayOfWeek.Wednesday:
                    return 3;
                case DayOfWeek.Thursday:
                    return 4;
                case DayOfWeek.Friday:
                    return 5;
                case DayOfWeek.Saturday:
                    return 6;
                case DayOfWeek.Sunday:
                    return 7;
                default:
                    return -1;
            }
        }
        
        public DateTime NextSchedule
        {
            get { return _nextschedule; }
        }
        
        private new double Interval
        {
            get { return base.Interval; }
        }
    }
}
